Skip to content

Conversation

@shkudun
Copy link

@shkudun shkudun commented Jan 25, 2026

Summary

  • Adds a Harkan‑style cyberpunk game example for Abstract Testnet.
  • Includes AGW login, optional paymaster flow, and on‑chain score submission.
  • Provides public demo for quick review.

Demo

https://abs-game001.vercel.app/

Contract (Abstract Testnet)

0x51F2C923a5307E2701F228DcC4cB3D72B1aAb804

Test plan


PR-Codex overview

This PR introduces a cyberpunk mini-game titled Harkan ABS, designed for the Abstract Testnet. It includes features like on-chain score submission, a leaderboard, and local running instructions, along with a user interface for interacting with a smart contract.

Detailed summary

  • Added README.md with game description, demo link, contract address, and features.
  • Created agw-panel.js for wallet integration and score submission.
  • Developed index.html for the game interface, including a leaderboard and player profile.
  • Styled the game with styles.css.
  • Implemented game logic in app.js, including score tracking and gameplay mechanics.

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

@cursor
Copy link

cursor bot commented Jan 25, 2026

PR Summary

Introduces a standalone example app under examples/harkan-abs showcasing wallet integration and on-chain interactions on Abstract.

  • New static frontend: index.html + styles.css and game logic in app.js (canvas shooter, HUD, difficulty, sounds)
  • Wallet UX: network switch, EIP-1193 connect, status, and contract address handling with explorer links
  • On-chain: submitScore via Viem, persists last tx, and reads ScoreSubmitted logs to render top scores
  • React AGW panel (agw-panel.js): AGW login/logout, submit via direct or sponsored tx with configurable paymaster, uses @abstract-foundation/agw-react
  • README.md with demo link, deployed contract, features, and local run/usage instructions

Written by Cursor Bugbot for commit 1c50ce1. This will update automatically on new commits. Configure here.

@vercel
Copy link

vercel bot commented Jan 25, 2026

@shkudun is attempting to deploy a commit to the Abstract Foundation Team on Vercel.

A member of the Team first needs to authorize it.

handleHit();
} else {
handleMiss();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overlapping targets removed but only one hit counted

Low Severity

When a click lands on overlapping targets, the filter removes all of them from game.targets, but handleHit() is only called once afterward. This causes players to lose potential points — multiple targets disappear while only one hit is credited.

Fix in Cursor Fix in Web

const name =
profile.name && entry.player === walletAddress
? profile.name
: `${entry.player.slice(0, 6)}...${entry.player.slice(-4)}`;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Address comparison fails due to case sensitivity

Medium Severity

The comparison entry.player === walletAddress uses strict equality between addresses with different casing. The walletAddress from eth_requestAccounts is typically lowercase, while entry.player from viem's log decoding is checksummed (mixed case). This case-sensitive comparison will always fail, so the user's profile name is never displayed next to their score in the on-chain leaderboard — only the truncated address appears.

Fix in Cursor Fix in Web


saveBtn.addEventListener("click", saveLocalScore);
submitBtn.addEventListener("click", submitOnChain);
refreshLeaderboard.addEventListener("click", renderLeaderboard);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refresh button shows local scores instead of on-chain

Medium Severity

The refreshLeaderboard button click handler is bound to renderLeaderboard(), which displays locally-stored scores. However, the button is in the LEADERBOARD panel alongside an on-chain transaction indicator, and the app is designed to show on-chain data when a contract is configured. The button likely needs to call refreshOnChainLeaderboard() instead, since clicking Refresh currently replaces any displayed on-chain leaderboard entries with local scores.

Fix in Cursor Fix in Web

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall. One non-blocking note: the example relies on CDN imports via esm.sh (React/Wagmi/Viem/AGW). That’s fine for a demo, but if the examples repo prefers local deps, we might want to add a package.json + local installs later. No functional issues found.

if (age > target.ttl) {
handleMiss();
return false;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Targets expire during pause causing unfair misses

Medium Severity

When the game is paused, performance.now() continues advancing while target born timestamps remain unchanged. Upon resuming, drawTargets() calculates target age as now - target.born, which includes the pause duration. Targets that were active before pausing may immediately exceed their TTL and trigger handleMiss(), penalizing the player for time they couldn't interact with the game.

Additional Locations (1)

Fix in Cursor Fix in Web

} catch (error) {
setStatus("Transaction failed");
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing guard allows duplicate on-chain submissions

Medium Severity

The submitOnChain function has no guard to prevent concurrent submissions. While the transaction is pending (awaiting writeContract), the submit button remains enabled and canSubmitScore stays true. Users can click multiple times, triggering duplicate on-chain transactions for the same score, wasting gas and creating duplicate leaderboard entries. The React panel correctly uses isSubmitting to disable its button, but the vanilla JS version lacks equivalent protection.

Fix in Cursor Fix in Web

overlay.querySelector("p").textContent =
"Use your mouse or tap to lock onto signals. Each hit boosts your sync score.";
openModal();
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enter Simulation button incorrectly opens help modal

Medium Severity

The enterBtn ("Enter Simulation") click handler sets up the game overlay correctly, but then incorrectly calls openModal() at line 803 which opens the "How it works" help dialog. This is the same modal that the separate howBtn ("How It Works") button opens at line 796. Users clicking "Enter Simulation" would unexpectedly see the help modal appear, obscuring the game area. The openModal() call appears to be an accidental inclusion.

Fix in Cursor Fix in Web

.request({ method: "eth_chainId" })
.then(setChainInfo)
.catch(() => setChainInfo(null));
window.ethereum.on("chainChanged", setChainInfo);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing handler for wallet account changes

Medium Severity

The code handles the chainChanged event but is missing a handler for accountsChanged. When a user switches accounts in their wallet (e.g., MetaMask), walletAddress and walletClient remain stale. The UI continues displaying the old address, and submitOnChain() passes the old account to writeContract() at line 555. This causes a mismatch between the displayed account and the actual connected account, leading to failed or misdirected transactions.

Fix in Cursor Fix in Web

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

updateProfile(entry);
renderLeaderboard();
setStatus("Score saved locally", true);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scores can be saved multiple times per run

Medium Severity

After a run completes, canSubmitScore is set to true but neither saveLocalScore nor submitOnChain resets it to false after saving. The save/submit buttons remain enabled, allowing users to click "Save Local" repeatedly to add duplicate entries to the leaderboard and inflate profile stats (runs count, total hits/misses) with the same score data.

Additional Locations (1)

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant